package graphics;

import geography.Location;
import geography.Maze;
import geography.Orientation;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.KeyEvent;

import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.KeyStroke;

import player.Player;
import player.PlayerMove;
import player.UserAction;
import structure.MazeNode;
import ui.GameGUI;
import ui.InterfaceElement;
import ui.InterfaceSprite;

/**
 * This {@link JPanel} contains the {@link Tile}s.
 * @author Nils Klement
 *
 */
public class MazePanel extends JPanel{
	
	private static final long serialVersionUID = 1L;
	private Dimension panelDimension;
	private Dimension tileNumber;
	private Tile[][] tiles;
	
	private GameGUI gui;
	private int tileSize;
	private Player player;
	private Location startingPosition;
	private Location exit;
	
	private PlayerMove upMove;
	private PlayerMove downMove;
	private PlayerMove leftMove;
	private PlayerMove rightMove;
	
	private UserAction pressSpace;
	private int level;
	
	public MazePanel(Maze gameMaze, GameGUI guiIn, int depth){
		tileSize = 28;
		if(gameMaze == null){
			throw new IllegalArgumentException("The game maze is not set.");
		}
		level  = depth;
		gui = guiIn;
		MazeNode[][] nodes = gameMaze.getTiles();
		tiles = new Tile[nodes.length][nodes[0].length];
		for(int i=0;i<nodes.length;i++){
			for(int j=0;j<nodes[i].length;j++){
				tiles[i][j] = new Tile(nodes[i][j]);				
			}
		}
		startingPosition = gameMaze.getStartingLocation();
		exit = gameMaze.getExitLocation();
		Orientation facing;
		if(tiles[startingPosition.getX()][startingPosition.getY()].getEast() != null){
			facing = Orientation.EAST;
		}
		else if(tiles[startingPosition.getX()][startingPosition.getY()].getWest() != null){
			facing = Orientation.WEST;
		}
		else if(tiles[startingPosition.getX()][startingPosition.getY()].getNorth() != null){
			facing = Orientation.NORTH;
		}
		else{
			facing = Orientation.SOUTH;
		}
		
		setFocusable(true);
		
		player = new Player(startingPosition, facing);
		
		setKeyBindings();
				
		setOpaque(false);
		
		tileNumber = new Dimension(gameMaze.getDimensions().width,gameMaze.getDimensions().height);
		panelDimension = new Dimension(tileNumber.width*tileSize,tileNumber.height*tileSize);
		setPreferredSize(panelDimension);
		
		//the starting-position is made visible right away
		illuminateAroundPlayer();
	}
	
	protected void paintComponent(Graphics g){
		super.paintComponents(g);
		paintTiles(g);
		paintStartingTile(g);
		paintExitTile(g);
		
		paintPlayer(g);
		if(player.getPosition().getX() < tileNumber.width-5 || player.getPosition().getY() < tileNumber.height-1){
			paintLevelTag(g);
		}
	}
	
	//draws the bottom-most layer of the maze
	private void paintTiles(Graphics g){
		for(int i=0;i<tiles.length;i++){
			for(int j=0;j<tiles[0].length;j++){
				g.drawImage(tiles[i][j].getImage(), i*tileSize, j*tileSize, Color.black, null);
			}
		}
	}
	
	private void paintPlayer(Graphics g){
		g.drawImage(player.getImage(), player.getPosition().getX()*tileSize, player.getPosition().getY()*tileSize, null, null);
	}
	
	private void paintStartingTile(Graphics g){
		Tile startingTile = tiles[startingPosition.getX()][startingPosition.getY()];
		if(!startingTile.isUncovered()){
			return;
		}
		boolean illuminated = startingTile.isIlluminated();
		int xCoord = startingPosition.getX()*tileSize-10, yCoord = startingPosition.getY()*tileSize-10;
		g.drawImage(TileSprite.loadObject(TileObject.LADDER, illuminated), xCoord, yCoord, null, null);
	}
	
	private void paintExitTile(Graphics g){
		Tile exitTile = tiles[exit.getX()][exit.getY()];
		if(!exitTile.isUncovered()){
			return;
		}
		boolean illuminated = exitTile.isIlluminated();
		int xCoord = exit.getX()*tileSize, yCoord = exit.getY()*tileSize;
		if(exitTile.getEast() == null){
			g.drawImage(TileSprite.loadObject(TileObject.EXIT_EAST, illuminated), xCoord, yCoord, null, null);
		}
		else{
			g.drawImage(TileSprite.loadObject(TileObject.EXIT_WEST, illuminated), xCoord, yCoord, null, null);
		}
	}
	
	private void paintLevelTag(Graphics g){
		int xCoord = panelDimension.width-124, yCoord = panelDimension.height-32;
		g.drawImage(InterfaceSprite.getImage(InterfaceElement.LEVEL_TAG), xCoord, yCoord, null, null);
		if(level > 9){
			int levelDecim = level/10;
			g.drawImage(InterfaceSprite.getNumber(levelDecim), xCoord+90, yCoord, null, null);
		}
		g.drawImage(InterfaceSprite.getNumber(level%10), xCoord+107, yCoord, null, null);
	}
	
	private void illuminateAroundPlayer(){
		int xLight = player.getPosition().getX(), yLight = player.getPosition().getY();
		if(xLight < 0 || xLight >= tileNumber.width || yLight < 0 || yLight >= tileNumber.height){
			throw new IllegalArgumentException("Location does not exist in the maze.");
		}
		
		for(int i=0;i<tiles.length;i++){
			for(int j=0;j<tiles[i].length;j++){
				tiles[i][j].darken();
				tiles[i][j].shroud();
			}
		}

		tiles[xLight][yLight].uncover();
		tiles[xLight][yLight].lighten();
		
		Location northLocation = tiles[xLight][yLight].getNorth();
		if(northLocation != null){
			Tile northTile = tiles[xLight][yLight-1];
			northTile.uncover();
			northTile.lighten();
			if(northTile.getNorth() != null){
				tiles[xLight][yLight-2].uncover();
			}
		}
		Location southLocation = tiles[xLight][yLight].getSouth();
		if(southLocation != null){
			Tile southTile = tiles[xLight][yLight+1];
			southTile.uncover();
			southTile.lighten();
			if(southTile.getSouth() != null){
				tiles[xLight][yLight+2].uncover();
			}
		}
		Location eastLocation = tiles[xLight][yLight].getEast();
		if(eastLocation != null){
			Tile eastTile = tiles[xLight+1][yLight];
			eastTile.uncover();
			eastTile.lighten();
			if(eastTile.getEast() != null){
				tiles[xLight+2][yLight].uncover();
			}
		}
		Location westLocation = tiles[xLight][yLight].getWest();
		if(westLocation != null){
			Tile westTile = tiles[xLight-1][yLight];
			westTile.uncover();
			westTile.lighten();
			if(westTile.getWest() != null){
				tiles[xLight-2][yLight].uncover();
			}
		}		
	}
	
	public void movePlayer(Orientation direction){
		//the player's facing is always changed
		player.changeFacing(direction);
		int xPlayer = player.getPosition().getX(), yPlayer = player.getPosition().getY();
		switch (direction) {
		case NORTH:
			if(tiles[xPlayer][yPlayer].getNorth() != null){
				player.moveTo(tiles[xPlayer][yPlayer].getNorth());
			}
			break;
		case SOUTH:
			if(tiles[xPlayer][yPlayer].getSouth() != null){
				player.moveTo(tiles[xPlayer][yPlayer].getSouth());
			}
			break;
		case EAST:
			if(tiles[xPlayer][yPlayer].getEast() != null){
				player.moveTo(tiles[xPlayer][yPlayer].getEast());
			}
			break;
		case WEST:
			if(tiles[xPlayer][yPlayer].getWest() != null){
				player.moveTo(tiles[xPlayer][yPlayer].getWest());
			}
			break;
		default:
			break;
		}
		
		illuminateAroundPlayer();
		repaint();
	}
	
	private void setKeyBindings(){
		upMove = new PlayerMove(this, Orientation.NORTH);
		downMove = new PlayerMove(this, Orientation.SOUTH);
		leftMove = new PlayerMove(this, Orientation.WEST);
		rightMove = new PlayerMove(this, Orientation.EAST);
		
		pressSpace = new UserAction(this);
		
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "moveUp");
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "movedown");
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "moveLeft");
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "moveRight");
		
		getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "spaceDown");

		
		getActionMap().put("moveUp", upMove);
		getActionMap().put("movedown", downMove);
		getActionMap().put("moveLeft", leftMove);
		getActionMap().put("moveRight", rightMove);
		
		getActionMap().put("spaceDown", pressSpace);		

	}
	
	public void makeUserAction(){
		if(player.getPosition().equals(exit)){
			gui.newLevel();
		}
	}

}
